<template>
    <div class="calendar">
        <div class="header">
            <div class="title header-info" style="width: 50%;">{{ title }}</div>
            <div class="tools" style="width: 50%;">
                <div :class="`prev-button header-info tool-button ${disablePrevMonth ? 'button-disable' : ''}`"
                    @click="prevMonth">上个月</div>
                <div class="today-button header-info tool-button" @click="toToday">今日</div>
                <div class="next-button header-info tool-button" @click="nextMonth">下个月</div>
            </div>
        </div>
        <div class="weekdays">
            <div v-for="day in daysOfWeek" :key="day" class="weekday">{{ day }}</div>
        </div>
        <div class="days" @wheel.native.prevent="scroll">
            <div v-for="day in days" :key="day.date" :class="getDaysClass(day)" @click="select(day)">
                <div :class="getDayClass(day)">
                    <slot v-if="$slots.default" :day="day"></slot>
                    <div v-else>
                        {{ day.day }}
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup>
const props = defineProps({
    modelValue: Date,
    disableDate: {
        type: Function,
        default: () => {
            return false
        }
    }
})

const jumpToMonth = (date) => {
    currentDate.value = new Date(date.getFullYear(), date.getMonth(), 1)
}

const scroll = (e) => {
    const eventDelta = e.wheelDelta || -e.deltaY * 40
    if (eventDelta > 0) {
        prevMonth()
    } else {
        nextMonth()
    }
}

const emits = defineEmits(["update:modelValue", "change"])

const weekdays = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"]

const getEndOfDay = (date) => {
    const day = new Date(date)
    day.setHours(23, 59, 59, 999)
    return day
}

const currentDate = ref(new Date())
const selectedDate = ref(props.modelValue || currentDate.value)

const daysOfWeek = computed(() => {
    return weekdays
})

const getDaysClass = (day) => {
    return { notCurrentMonth: isNotCurrentMonth(day), day: true, isDisable: props.disableDate(day.date) }
}

const getDayClass = (day) => {
    return { dayNumber: true, today: isToday(day) && !isNotCurrentMonth(day), selected: isSelected(day) && !isNotCurrentMonth(day), }
}

const days = computed(() => {
    const year = currentDate.value.getFullYear()
    const month = currentDate.value.getMonth()
    const daysInMonth = new Date(year, month + 1, 0).getDate()
    const daysInLastMonth = new Date(year, month, 0).getDate()
    const firstDayOfMonth = new Date(year, month, 1).getDay()

    const days = []
    let day = 1
    let lastMonthDay = daysInLastMonth - firstDayOfMonth + 1
    let nextMonthDay = 1

    for (let i = 0; i < 6 * 7; i++) {
        if (i < firstDayOfMonth) {
            days.push({
                date: new Date(year, month - 1, lastMonthDay),
                day: lastMonthDay,
                isLastMonth: true,
                isNextMonth: false,
            })
            lastMonthDay++
        } else if (i >= firstDayOfMonth + daysInMonth) {
            days.push({
                date: new Date(year, month + 1, nextMonthDay),
                day: nextMonthDay,
                isLastMonth: false,
                isNextMonth: true,
            })
            nextMonthDay++
        } else {
            const date = new Date(year, month, day)
            days.push({ date, day, isLastMonth: false, isNextMonth: false })
            day++
        }
    }

    return days
})

const title = computed(
    () => `${currentDate.value.toLocaleString("default", { month: "long" })} ${currentDate.value.getFullYear()}`
)

const disablePrevMonth = $computed(() => {
    const date = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth(), 0)
    return props.disableDate(date)
})

const prevMonth = () => {
    if (disablePrevMonth) return
    currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)
    emits("change", currentDate.value)
}

const nextMonth = () => {
    currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() + 1, 1)
    emits("change", currentDate.value)
}

const toToday = () => {
    const today = new Date()
    currentDate.value = today
    select({
        date: today,
        day: today.getDate(),
        isLastMonth: false,
        isNextMonth: false,
    })
}

const isToday = (day) => {
    const today = new Date()
    return day.date.toDateString() === today.toDateString()
}

const isSelected = (day) => {
    return day.date.toDateString() === selectedDate.value.toDateString()
}

const isNotCurrentMonth = (day) => {
    return day.isLastMonth || day.isNextMonth
}

const select = (day) => {
    if (props.disableDate(day.date)) return
    if (day.isLastMonth) prevMonth()
    if (day.isNextMonth) nextMonth()
    selectedDate.value = getEndOfDay(day.date)
    emits("update:modelValue", selectedDate.value)
}

onMounted(() => {
    jumpToMonth(selectedDate.value)
    selectedDate.value = getEndOfDay(selectedDate.value)
    emits("update:modelValue", selectedDate.value)
})

defineExpose({ prevMonth, nextMonth })
</script>
<style scoped>
.calendar {
    font-size: var(--font-size);
    font-family: var(--font-family);
    height: 100%;
    width: 100%;
}

.header {
    display: flex;
    align-items: center;
    padding: 0 50px;
    height: calc(15% - 2px);
    background: rgba(0, 0, 0, .1);
    margin-bottom: 2px;
}

.header-info {
    height: 100%;
    display: flex;
    align-items: center;
    padding: 0 10px;
}

.tools {
    display: flex;
    justify-content: flex-end;
    height: 100%;
    gap: 5px;
    align-items: center;
}

.title {
    font-size: calc(var(--font-size) + 3px);
    font-weight: var(--font-weight);
    justify-content: center;
}

.weekdays {
    display: flex;
    flex-wrap: wrap;
    /* grid-gap: 10px; */
    text-align: center;
    height: calc(10% - 1px);
    background: rgba(0, 0, 0, .1);
    margin-bottom: 1px;
}

.weekday {
    width: calc(100% / 7);
    line-height: calc(var(--font-size) * 2);
    display: flex;
    justify-content: center;
    align-items: center;
}

.days {
    display: flex;
    flex-wrap: wrap;
    /* grid-gap: 10px; */
    text-align: center;
    height: 75%;
}

.day {
    width: calc(100% / 7 - 2px);
    height: calc(100% / 6);
    display: flex;
    justify-content: center;
    align-items: center;
    background: rgba(0, 0, 0, .1);
    margin: 1px;
}

.today {
    background-color: rgb(161 190 251 / 50%);
}

.selected {
    height: 100%;
    width: 100%;
    background: rgba(0, 0, 0, .2);
    border-radius: var(--border-radius);
}

.notCurrentMonth {
    color: #9d9d9d;
}

:deep(.dayNumber) {
    height: 100%;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.tool-button {
    background: rgba(0, 0, 0, .1);
    border-radius: var(--border-radius);
    height: 50px;
    aspect-ratio: 2;
    align-items: center;
    justify-content: center;
    max-height: 50%;
}

.tool-button:hover {
    background: rgba(0, 0, 0, .2);
}

.isDisable {
    color: #ccc;
}

.button-disable {
    background: rgba(208, 208, 208, .4);
}

.button-disable:hover {
    background: rgba(208, 208, 208, .4);
}
</style>